﻿using System;
using System.Collections.Specialized;
using System.Configuration;
using System.Configuration.Provider;
using System.Data.Entity;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Security;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Configuration;
using System.Web.SessionState;
using Tiscs.Practices.Diagnostics;
using Tiscs.Practices.Web.Providers.Entities;

namespace Tiscs.Practices.Web.Providers
{
    /// <summary>
    /// 会话状态存储提供程序。
    /// </summary>
    public abstract class DefaultSessionStateProvider : SessionStateStoreProviderBase
    {
        #region Abstract Methods

        /// <summary>
        /// 创建DbContext实例。
        /// </summary>
        /// <returns>DbContext实例。</returns>
        protected abstract DbContext CreateDbContext();

        #endregion

        #region Static Methods

        private static string AppendAppIdHash(string id)
        {
            string appDomainAppId = AppDomain.CurrentDomain.GetData(".appId") as string ?? HttpRuntime.AppDomainAppId;
            string hash = appDomainAppId.GetHashCode().ToString("X8", CultureInfo.InvariantCulture);
            if (id.EndsWith(hash, StringComparison.OrdinalIgnoreCase))
            {
                return id;
            }
            return id + hash;
        }

        private static SessionEntity NewSession(string id, int timeout)
        {
            DateTime utcNow = DateTime.UtcNow;
            SessionEntity session = new SessionEntity()
            {
                Created = utcNow,
                SessionId = id,
                Timeout = timeout,
                Expires = utcNow.AddMinutes((double)timeout),
                Locked = false,
                LockDate = utcNow,
                LockCookie = 0,
                Flags = 0
            };
            return session;
        }

        private static void Serialize(SessionStateStoreData item, Stream stream)
        {
            bool flag = true;
            bool flag2 = true;
            BinaryWriter binaryWriter = new BinaryWriter(stream);
            binaryWriter.Write(item.Timeout);
            if (item.Items == null || item.Items.Count == 0)
            {
                flag = false;
            }
            binaryWriter.Write(flag);
            if (item.StaticObjects == null || item.StaticObjects.NeverAccessed)
            {
                flag2 = false;
            }
            binaryWriter.Write(flag2);
            if (flag)
            {
                ((SessionStateItemCollection)item.Items).Serialize(binaryWriter);
            }
            if (flag2)
            {
                item.StaticObjects.Serialize(binaryWriter);
            }
            binaryWriter.Write(255);
        }

        private static void SerializeStoreData(SessionStateStoreData item, int initialStreamSize, out byte[] buf, out int length, bool compressionEnabled)
        {
            using (MemoryStream memoryStream = new MemoryStream(initialStreamSize))
            {
                DefaultSessionStateProvider.Serialize(item, memoryStream);
                if (compressionEnabled)
                {
                    byte[] buffer = memoryStream.GetBuffer();
                    int count = (int)memoryStream.Length;
                    memoryStream.SetLength(0L);
                    using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionMode.Compress, true))
                    {
                        deflateStream.Write(buffer, 0, count);
                    }
                    memoryStream.WriteByte(255);
                }
                buf = memoryStream.GetBuffer();
                length = (int)memoryStream.Length;
            }
        }

        private static SessionStateStoreData Deserialize(HttpContext context, Stream stream)
        {
            int timeout;
            SessionStateItemCollection sessionItems;
            HttpStaticObjectsCollection staticObjects;
            try
            {
                BinaryReader binaryReader = new BinaryReader(stream);
                timeout = binaryReader.ReadInt32();
                bool flag = binaryReader.ReadBoolean();
                bool flag2 = binaryReader.ReadBoolean();
                if (flag)
                {
                    sessionItems = SessionStateItemCollection.Deserialize(binaryReader);
                }
                else
                {
                    sessionItems = new SessionStateItemCollection();
                }
                if (flag2)
                {
                    staticObjects = HttpStaticObjectsCollection.Deserialize(binaryReader);
                }
                else
                {
                    staticObjects = SessionStateUtility.GetSessionStaticObjects(context);
                }
                byte b = binaryReader.ReadByte();
                if (b != 255)
                {
                    throw new HttpException(ProvidersHelper.Invalid_Session_State);
                }
            }
            catch (EndOfStreamException)
            {
                throw new HttpException(ProvidersHelper.Invalid_Session_State);
            }
            return new SessionStateStoreData(sessionItems, staticObjects, timeout);
        }

        private static SessionStateStoreData DeserializeStoreData(HttpContext context, Stream stream, bool compressionEnabled)
        {
            if (compressionEnabled)
            {
                using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress, true))
                {
                    return DefaultSessionStateProvider.Deserialize(context, deflateStream);
                }
            }
            return DefaultSessionStateProvider.Deserialize(context, stream);
        }

        private static void SaveItemToSession(SessionEntity session, SessionStateStoreData item, bool compressionEnabled)
        {
            byte[] sessionItem = null;
            int num = 0;
            DefaultSessionStateProvider.SerializeStoreData(item, 7000, out sessionItem, out num, compressionEnabled);
            session.SessionItem = sessionItem;
        }

        private static SessionStateStoreData InitializeSessionItem(HttpContext context, SessionEntity session, bool compressionEnabled)
        {
            SessionStateStoreData sessionStateStoreData = new SessionStateStoreData(new SessionStateItemCollection(), SessionStateUtility.GetSessionStaticObjects(context), session.Timeout);
            DefaultSessionStateProvider.SaveItemToSession(session, sessionStateStoreData, compressionEnabled);
            session.Flags = 0;
            return sessionStateStoreData;
        }

        private static void ReleaseItemNoSave(DbContext db, string id, object lockId)
        {
            id = DefaultSessionStateProvider.AppendAppIdHash(id);
            SessionEntity session = db.GetSession(id);
            if (session != null && session.Locked && session.LockCookie == (int)lockId)
            {
                session.Locked = false;
            }
        }

        private static Exception ValidateSessionId(string paramName, string sessionId)
        {
            return Validation.Parameter<string>(paramName).AssertNotNull()
                .Assert(p => p.Length <= SessionIDManager.SessionIDMaxLength, n => new ArgumentException(ProvidersHelper.Session_Id_Too_Long, n)).Validate(sessionId);
        }

        #endregion

        #region Private Methods

        private bool CanPurge()
        {
            long ticks = DateTime.UtcNow.Ticks;
            return TimeSpan.FromTicks(ticks - this.LastSessionPurgeTicks).TotalSeconds > 30.0;
        }

        private void PurgeIfNeeded()
        {
            if (this.CanPurge())
            {
                Task task = new Task(delegate
                {
                    this.PurgeExpiredSessions();
                });
                task.Start();
            }
        }

        private void PurgeExpiredSessions()
        {
            try
            {
                using (DbContext db = CreateDbContext())
                {
                    IQueryable<SessionEntity> queryable = from s in db.GetSessions() where s.Expires < DateTime.UtcNow select s;
                    foreach (SessionEntity current in queryable)
                    {
                        db.GetSessions().Remove(current);
                    }
                    db.SaveChanges();
                    this.LastSessionPurgeTicks = DateTime.UtcNow.Ticks;
                }
            }
            catch
            {
            }
        }

        private SessionStateStoreData DoGet(HttpContext context, string id, bool exclusive, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
        {
            id = DefaultSessionStateProvider.AppendAppIdHash(id);
            locked = false;
            lockAge = TimeSpan.Zero;
            lockId = null;
            actions = SessionStateActions.None;
            using (DbContext db = CreateDbContext())
            {
                SessionEntity session = db.GetSession(id);
                if (session != null && session.Expires > DateTime.UtcNow)
                {
                    session.Expires = DateTime.UtcNow.AddMinutes((double)session.Timeout);
                    locked = session.Locked;
                    lockId = session.LockCookie;
                    SessionStateStoreData result = null;
                    if (locked)
                    {
                        lockAge = TimeSpan.FromSeconds((double)(DateTime.UtcNow - session.LockDate).Seconds);
                    }
                    else
                    {
                        if (exclusive)
                        {
                            session.Locked = true;
                            session.LockDate = DateTime.UtcNow;
                        }
                        byte[] sessionItem = session.SessionItem;
                        if (session.Flags == 1)
                        {
                            result = DefaultSessionStateProvider.InitializeSessionItem(context, session, this.CompressionEnabled);
                        }
                        else
                        {
                            using (MemoryStream memoryStream = new MemoryStream(sessionItem))
                            {
                                result = DefaultSessionStateProvider.DeserializeStoreData(context, memoryStream, this.CompressionEnabled);
                            }
                        }
                    }
                    db.SaveChanges();
                    return result;
                }
            }
            return null;
        }

        #endregion

        #region Private Properties

        private bool CompressionEnabled { get; set; }

        private long _lastSessionPurgeTicks;

        private long LastSessionPurgeTicks
        {
            get
            {
                return Interlocked.Read(ref this._lastSessionPurgeTicks);
            }
            set
            {
                Interlocked.Exchange(ref this._lastSessionPurgeTicks, value);
            }
        }

        #endregion

        #region SessionStateStoreProviderBase

        /// <summary>
        /// 初始化会话状态提供程序。
        /// </summary>
        /// <param name="name">会话状态提供程序的友好名称。</param>
        /// <param name="config">会话状态提供程序初始化配置。</param>
        public override void Initialize(string name, NameValueCollection config)
        {
            if (config == null)
            {
                throw new ArgumentNullException("config");
            }
            if (string.IsNullOrEmpty(name))
            {
                name = "DefaultSessionStateProvider";
            }
            base.Initialize(name, config);
            try
            {
                SessionStateSection sessionStateSection = (SessionStateSection)ConfigurationManager.GetSection("system.web/sessionState");
                CompressionEnabled = sessionStateSection.CompressionEnabled;
            }
            catch (SecurityException)
            {
            }
            if (config.Count > 0)
            {
                throw new ProviderException(string.Format(ProvidersHelper.Unrecognized_Attribute, config.GetKey(0)));
            }
        }

        /// <summary>
        /// 创建新的会话数据。
        /// </summary>
        /// <param name="context">当前请求的上下文实例。</param>
        /// <param name="timeout">会话数据的超时时间。</param>
        /// <returns>新的会话数据。</returns>
        public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout)
        {
            HttpStaticObjectsCollection collection = context == null ? null : SessionStateUtility.GetSessionStaticObjects(context);
            return new SessionStateStoreData(new SessionStateItemCollection(), collection, timeout);
        }

        /// <summary>
        /// 创建未初始化的会话数据。
        /// </summary>
        /// <param name="context">当前请求的上下文实例。</param>
        /// <param name="id">会话唯一标识。</param>
        /// <param name="timeout">会话数据的超时时间。</param>
        public override void CreateUninitializedItem(HttpContext context, string id, int timeout)
        {
            Exception exception = ValidateSessionId("id", id);
            if (exception != null)
            {
                throw exception;
            }
            id = AppendAppIdHash(id);
            using (DbContext db = CreateDbContext())
            {
                if (db.GetSession(id) == null)
                {
                    SessionEntity session = DefaultSessionStateProvider.NewSession(id, timeout);
                    SessionStateStoreData item = new SessionStateStoreData(new SessionStateItemCollection(), SessionStateUtility.GetSessionStaticObjects(context), session.Timeout);
                    DefaultSessionStateProvider.SaveItemToSession(session, item, this.CompressionEnabled);
                    db.GetSessions().Add(session);
                    db.SaveChanges();
                }
            }
        }

        /// <summary>
        /// 释放分配的资源。
        /// </summary>
        public override void Dispose()
        {
        }

        /// <summary>
        /// 在请求结束时执行。
        /// </summary>
        /// <param name="context">当前请求的上下文实例。</param>
        public override void EndRequest(HttpContext context)
        {
            this.PurgeIfNeeded();
        }

        /// <summary>
        /// 获取只读会话数据。
        /// </summary>
        /// <param name="context">当前请求的上下文实例。</param>
        /// <param name="id">会话唯一标识。</param>
        /// <param name="locked">会话是否锁定。</param>
        /// <param name="lockAge">会话数据的锁定时间。</param>
        /// <param name="lockId">会话数据锁定标识。</param>
        /// <param name="actions">会话数据是否需要初始化。</param>
        /// <returns>只读会话数据实例。</returns>
        public override SessionStateStoreData GetItem(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
        {
            Exception exception = ValidateSessionId("id", id);
            if (exception != null)
            {
                throw exception;
            }
            return this.DoGet(context, id, false, out locked, out lockAge, out lockId, out actions);
        }

        /// <summary>
        /// 获取独占的会话数据。
        /// </summary>
        /// <param name="context">当前请求的上下文实例。</param>
        /// <param name="id">会话唯一标识。</param>
        /// <param name="locked">会话是否锁定。</param>
        /// <param name="lockAge">会话数据的锁定时间。</param>
        /// <param name="lockId">会话数据锁定标识。</param>
        /// <param name="actions">会话数据是否需要初始化。</param>
        /// <returns>只读会话数据实例。</returns>
        public override SessionStateStoreData GetItemExclusive(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
        {
            Exception exception = ValidateSessionId("id", id);
            if (exception != null)
            {
                throw exception;
            }
            return this.DoGet(context, id, true, out locked, out lockAge, out lockId, out actions);
        }

        /// <summary>
        /// 初始化请求。
        /// </summary>
        /// <param name="context">当前请求的上下文实例。</param>
        public override void InitializeRequest(HttpContext context)
        {
        }

        /// <summary>
        /// 释放独占会话数据的锁定。
        /// </summary>
        /// <param name="context">当前请求的上下文实例。</param>
        /// <param name="id">会话唯一标识。</param>
        /// <param name="lockId">会话数据锁定标识。</param>
        public override void ReleaseItemExclusive(HttpContext context, string id, object lockId)
        {
            Exception exception = ValidateSessionId("id", id);
            if (exception != null)
            {
                throw exception;
            }
            using (DbContext db = CreateDbContext())
            {
                DefaultSessionStateProvider.ReleaseItemNoSave(db, id, lockId);
                db.SaveChanges();
            }
        }

        /// <summary>
        /// 删除会话数据。
        /// </summary>
        /// <param name="context">当前请求的上下文实例。</param>
        /// <param name="id">会话唯一标识。</param>
        /// <param name="lockId">会话数据锁定标识。</param>
        /// <param name="item">要删除的会话数据。</param>
        public override void RemoveItem(HttpContext context, string id, object lockId, SessionStateStoreData item)
        {
            Exception exception = ValidateSessionId("id", id);
            if (exception != null)
            {
                throw exception;
            }
            id = DefaultSessionStateProvider.AppendAppIdHash(id);
            using (DbContext db = CreateDbContext())
            {
                SessionEntity session = db.GetSession(id);
                if (session != null && session.LockCookie == (int)lockId)
                {
                    db.GetSessions().Remove(session);
                    db.SaveChanges();
                }
            }
        }

        /// <summary>
        /// 重置会话数据超时时间。
        /// </summary>
        /// <param name="context">当前请求的上下文实例。</param>
        /// <param name="id">会话唯一标识。</param>
        public override void ResetItemTimeout(HttpContext context, string id)
        {
            Exception exception = ValidateSessionId("id", id);
            if (exception != null)
            {
                throw exception;
            }
            id = DefaultSessionStateProvider.AppendAppIdHash(id);
            using (DbContext db = CreateDbContext())
            {
                SessionEntity session = db.GetSession(id);
                if (session != null)
                {
                    session.Expires = DateTime.UtcNow.AddMinutes((double)session.Timeout);
                    db.SaveChanges();
                }
            }
        }

        /// <summary>
        /// 设置并释放独占会话数据的锁定。
        /// </summary>
        /// <param name="context">当前请求的上下文实例。</param>
        /// <param name="id">会话唯一标识。</param>
        /// <param name="item">要保存的会话数据。</param>
        /// <param name="lockId">会话数据锁定标识。</param>
        /// <param name="newItem">是否标记为新的数据。</param>
        public override void SetAndReleaseItemExclusive(HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }
            Exception exception = ValidateSessionId("id", id);
            if (exception != null)
            {
                throw exception;
            }
            id = DefaultSessionStateProvider.AppendAppIdHash(id);
            using (DbContext db = CreateDbContext())
            {
                SessionEntity session = db.GetSession(id);
                if (session == null)
                {
                    if (newItem)
                    {
                        session = DefaultSessionStateProvider.NewSession(id, item.Timeout);
                        db.GetSessions().Add(session);
                    }
                    else
                    {
                        if (session == null)
                        {
                            throw new InvalidOperationException(ProvidersHelper.Session_not_found);
                        }
                    }
                }
                else
                {
                    if (lockId == null)
                    {
                        session.LockCookie = 0;
                    }
                    else
                    {
                        session.LockCookie = (int)lockId;
                    }
                    session.Locked = false;
                    session.Timeout = item.Timeout;
                }
                DefaultSessionStateProvider.SaveItemToSession(session, item, this.CompressionEnabled);
                db.SaveChanges();
            }
        }

        /// <summary>
        /// 设置会话超时回调方法。
        /// </summary>
        /// <param name="expireCallback">会话超时回调方法。</param>
        /// <returns>会话超时回调方法是否设置成功。</returns>
        public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback)
        {
            return false;
        }

        #endregion
    }
}
